/**
 * 
 */
package hu.myai.calculators;

import hu.myai.model.db.AxHistory;
import hu.myai.model.graph.AbstractNode;
import hu.myai.model.graph.FibNode;
import hu.myai.model.misc.KeyValue;
import hu.myai.model.sp.StockPivot;
import hu.myai.util.Constans;

import java.util.List;

/**
 * @author bozbalint
 * 
 */
public class Fibonacci extends AbstractCalculator {

	public Fibonacci(String symbol, StockPivot maxStock, StockPivot minStock, List<AxHistory> stockList,
			List<StockPivot> stockPivotList) {
		super(symbol, maxStock, minStock, stockList, stockPivotList);
		// TODO Auto-generated constructor stub
	}

	/**
	 * set the fibonacci levels for all stock pivot after the season turning
	 * point<br>
	 * 
	 * @param mode
	 *            0 - calculate between season high ans season low<br>
	 *            1 - guess the season highs and season lows
	 * @return the number of fibonacci level touch
	 */
	@Override
	public int calculator(int mode) {

		if (maxStock == null || minStock == null) {
			log.error("Error - calculateFibNode is not executable, season high or low is not defined");
			return -1;
		}

		double lastValue = stockPivotList.get(stockPivotList.size() - 1).getValue();

		// calculate lows and highs
		if (mode == 0) {

			// Scenario 1 - close future is between season low and season high
			double distanceMinMax = Math.abs(maxStock.getValue() - minStock.getValue());

			if (maxStock.getLocation().before(minStock.getLocation())) {

				// bear trend
				return setFibNodeValues(maxStock, distanceMinMax);
			} else {

				// bull trend
				return setFibNodeValues(minStock, distanceMinMax);
			}
		}

		// guess the fib levels
		if (mode == 1) {
			// if the last stock value is closer to the resistance (season max)
			if (lastValue > (maxStock.getValue() + minStock.getValue()) / 2) {

				// Scenario 1 - close to the season high, (higher high?)
				// bull trend
				return setFibNodeValues(minStock, calcFiboDistance(minStock));
			}
			// if the last stock value is closer to the support (season low)
			else {

				// Scenario 2 - close to the season low, (lower low?)
				// bear trend
				return setFibNodeValues(maxStock, calcFiboDistance(maxStock));
			}
		}
		return -1;
	}

	/**
	 * @param baseStockPivot
	 *            the fibonacci 0 level, this is the base or the levels
	 *            calculation
	 * @param distanceMinMax
	 *            the difference between season low and season high
	 * @return number of touch
	 */
	private int setFibNodeValues(StockPivot baseStockPivot, double distanceMinMax) {

		// init fig graph
		FibNode actFibNode = (FibNode) Constans.bf_fib.getBean("fib_000");

		// remove the set values from the stock Pivot list
		List<StockPivot> aftreMaxList = stockPivotList.subList(stockPivotList.indexOf(baseStockPivot), stockPivotList
				.size());

		int numberOfTouch = 0;

		for (StockPivot stockPivot : aftreMaxList) {

			// % of the change = distance from the base / (max-min)
			double actLevel = Math.abs(baseStockPivot.getValue() - stockPivot.getValue()) / distanceMinMax;
			FibNode fibNode = actFibNode.getNextNode(actLevel);

			if (fibNode != null) {
				actFibNode = fibNode;
				numberOfTouch++;
			}
			stockPivot.setFibNode(fibNode);

			stockPivot.setPercentFromSupport(actLevel);
		}
		return numberOfTouch;
	}

	/**
	 * @return calculated next possible Stock Pivot value based on fib level
	 */
	@Override
	public KeyValue[] getNexts() {

		StockPivot lastPivot = stockPivotList.get(stockPivotList.size() - 1);
		StockPivot lastPivot2 = stockPivotList.get(stockPivotList.size() - 2);

		double fibDistance = (lastPivot.getValue() - lastPivot2.getValue())
				/ (lastPivot.getPercentFromSupport() - lastPivot2.getPercentFromSupport());
		double fibZero = lastPivot.getValue() - (lastPivot.getPercentFromSupport() * fibDistance);

		// List<KeyValue> retValues = new ArrayList<KeyValue>();

		// double nextValue = fibZero + fibDistance;

		// levels next to last pivot
		KeyValue[] valuePairsFibFan = { null, null };
		boolean found = true;

		for (AbstractNode lastAbstractNode : getLastFibNode().getNextNodeList()) {

			// last pivot has fibo level
			FibNode lastFibNode = (FibNode) lastAbstractNode;

			// value that belongs to the Fib level
			double levelValue = fibZero + (fibDistance * lastFibNode.getFibLevel());

			if (levelValue > 0) {

				// closest values
				if (levelValue < lastPivot.getValue() && (found || valuePairsFibFan[1] == null)) {
					valuePairsFibFan[0] = new KeyValue(false, lastFibNode.getName(), StockPivot
							.valueNormalizer(levelValue), lastFibNode);
					found = false;
				} else if (levelValue > lastPivot.getValue() && (found || valuePairsFibFan[0] == null)) {
					valuePairsFibFan[1] = new KeyValue(false, lastFibNode.getName(), StockPivot
							.valueNormalizer(levelValue), lastFibNode);
					found = false;
				}
			}
		}
		return valuePairsFibFan;// StockPivot.valueNormalizer(nextValue);
	}

	/**
	 * @param seasonPivot
	 *            calculate a fibo level scenario
	 * @return the distance between fibo 0% and 100%
	 */
	private double calcFiboDistance(StockPivot seasonPivot) {

		StockPivot actStockPivotMax = null;
		int actStockPivotValueableMax = 0;
		int actStockPivotValueableMax2 = 0;
		double difference;
		int actStockPivotValueable;
		double actLevel;

		for (int i = stockPivotList.size() - 1; i >= 0; i--) {

			actStockPivotValueable = 0;

			// reference high, I find the 50% fibo level
			difference = Math.abs(stockPivotList.get(i).getValue() - seasonPivot.getValue()) * 2;

			// skip all difference where the 100% level goes minus or
			if (difference > seasonPivot.getValue() || difference == 0) {
				log.debug("bluffed distance: " + difference + "  -> skipped");
				continue;
			}

			// remove the set values from the stock Pivot list
			List<StockPivot> betweenPivots = stockPivotList.subList(stockPivotList.indexOf(seasonPivot), stockPivotList
					.size());

			// check whatever, remaining stock pivots are on fibo levels
			for (StockPivot stockPivot : betweenPivots) {

				// % of the change = distance from the base / (max-min)
				actLevel = Math.abs(seasonPivot.getValue() - stockPivot.getValue()) / difference;

				// 0% fibo node has a link to every level
				FibNode actFibNode = ((FibNode) Constans.bf_fib.getBean("fib_000")).getNextNode(actLevel);

				// !null if fibo level available
				if (actFibNode != null) {
					actStockPivotValueable++;
				}
				stockPivot.setPercentFromSupport(actLevel);
			}
			if (actStockPivotValueable >= actStockPivotValueableMax) {
				actStockPivotValueableMax2 = actStockPivotValueableMax;
				actStockPivotValueableMax = actStockPivotValueable;
				actStockPivotMax = stockPivotList.get(i);
			}
			if (actStockPivotValueableMax2 == actStockPivotValueableMax)
				log.warn("Warn - The Fibonacci level is not convinient " + actStockPivotValueableMax2);
		}

		difference = Math.abs(actStockPivotMax.getValue() - seasonPivot.getValue()) * 2;
		log.debug("Calculated 100% fibonacci distance: " + difference);

		return difference;
	}

	/**
	 * @return the last stock pivots that has a Fibonacci level assigned
	 */
	private FibNode getLastFibNode() {
		for (int i = stockPivotList.size(); i > 0; i--) {
			if (stockPivotList.get(i - 1).getFibNode() != null)
				return stockPivotList.get(i - 1).getFibNode();
		}
		return new FibNode();
	}

}
